iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0

什麼是 Zustand?

Zustand 是一個輕量級、快速且具擴展性的狀態管理解決方案,採用簡化的 Flux 原則,並基於發布/訂閱模式和 React hooks。

Zustand 簡化了 Flux 架構,主要是使用 store 來決定如何更改 state。一旦 store 傳回新狀態,UI 將使用更新的更改進行渲染。因此,Zustand 不會有 Flux 那樣的 Action 和 Dispatcher。除此之外,Zustand 的 store 是獨立於 React 的,即使是在非 React 的程式碼中也能夠使用。

使用範例

import { create } from "zustand";

const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}));

使用 create 函數建立一個 store,set 參數去改變你 state 中的狀態。

function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return <h1>{bears} around here ...</h1>;
}

function Controls() {
  const increasePopulation = useBearStore((state) => state.increasePopulation);
  return <button onClick={increasePopulation}>one up</button>;
}

Zustand 原始碼解析

跟 redux toolkit 相比,zustand 程式碼算相對好讀很多。

createStore

const createStoreImpl = (createState) => {
  let state; //用來存放全域的 state
  const listeners = new Set(); // 存放監聽器的集合

  const setState = (partial, replace) => {
    const nextState = typeof partial === "function" ? partial(state) : partial;

    //如果新狀態與舊狀態不同,則會更新狀態並通知所有訂閱的監聽器。
    if (nextState !== state) {
      const previousState = state;
      state =
        replace || typeof nextState !== "object" || nextState === null
          ? nextState
          : Object.assign({}, state, nextState);
      listeners.forEach((listener) => listener(state, previousState));
    }
  };

  const getState = () => state;

  const getInitialState = () => initialState;

  //subscribe 用來訂閱狀態變更,當狀態變更時,listener會被通知。
  const subscribe = (listener) => {
    listeners.add(listener);
    //當取消訂閱時,刪除監聽器
    return () => listeners.delete(listener);
  };

  const api = { setState, getState, getInitialState, subscribe };
  const initialState = (state = createState(setState, getState, api));

  return api;
};

const createStore = (createState) => createStoreImpl(createState);

useStore

export function useStore(api, selector = identity) {
  //useSyncExternalStore 是 React 18 的 Hook,詳細說明可參考下方內容。
  const slice = React.useSyncExternalStore(
    api.subscribe,
    () => selector(api.getState()),
    () => selector(api.getInitialState())
  );
  React.useDebugValue(slice); //在 React DevTools 中顯示 Hook 的值
  return slice;
}

create

const createImpl = (createState) => {
  const api = createStore(createState);

  const useBoundStore = (selector) => useStore(api, selector);

  Object.assign(useBoundStore, api);

  return useBoundStore;
};

useSyncExternalStore

useSyncExternalStore 是 React 18 的 Hook ,用於訂閱外部狀態,並在狀態變更時重新渲染組件。

使用上會長這樣:

const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
  • subscribe : 用於訂閱狀態變更的函數。
  • getSnapshot : 每次渲染時 React 會調用此函數以獲取當前狀態值。
  • getServerSnapshot (可選):用在伺服器端渲染(SSR)中,獲取 Server 端的初始狀態值。

useSyncExternalStore 原始碼

import * as React from "react";
const { useState, useEffect, useLayoutEffect, useDebugValue } = React;

export function useSyncExternalStore(subscribe, getSnapshot) {
  // 取得當前快照
  const value = getSnapshot();

  const [{ inst }, forceUpdate] = useState({
    inst: { value, getSnapshot },
  });

  //在瀏覽器重繪 (repaints) 前執行,同步檢查快照的改變,並根據情況強制更新
  useLayoutEffect(() => {
    inst.value = value;
    inst.getSnapshot = getSnapshot;

    if (checkIfSnapshotChanged(inst)) {
      forceUpdate({ inst });
    }
  }, [subscribe, value, getSnapshot]);

  //用來訂閱外部狀態的變更,當外部狀態改變時,會檢查新狀態是否和舊狀態不同,並強制重新渲染組件
  useEffect(() => {
    if (checkIfSnapshotChanged(inst)) {
      forceUpdate({ inst });
    }

    const handleStoreChange = () => {
      if (checkIfSnapshotChanged(inst)) {
        forceUpdate({ inst });
      }
    };

    return subscribe(handleStoreChange);
  }, [subscribe]);

  useDebugValue(value);
  return value;
}

function checkIfSnapshotChanged(inst) {
  const latestGetSnapshot = inst.getSnapshot;
  const prevValue = inst.value;
  try {
    const nextValue = latestGetSnapshot();
    return prevValue !== nextValue;
  } catch (error) {
    return true;
  }
}

參考資料:
https://github.com/pmndrs/zustand
https://juejin.cn/post/7178318352174022717
https://jimhuang.dev/react/zustand-source-code/
https://github.com/pmndrs/zustand/blob/main/src/index.ts
https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts
https://react.dev/reference/react/useSyncExternalStore
https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js


上一篇
Day 10 - Redux Toolkit 原理解析
下一篇
Day 12 - 掌握 Server State:為何你需要 Server State 管理與 React Query 的簡介
系列文
前進React 生態系 : 技術應用與概念解析22
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言